package org.salatec.tapestry4e.handlers;

import java.io.File;
import java.util.HashMap;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.salatec.tapestry4e.util.ClasspathUtils;
import org.salatec.tapestry4e.util.EditorUtils;
import org.salatec.tapestry4e.util.ProjectBuilder;

/**
 * Our sample handler extends AbstractHandler, an IHandler base class.
 * 
 * @see org.eclipse.core.commands.IHandler
 * @see org.eclipse.core.commands.AbstractHandler
 */
public class SwitchActionHandler extends AbstractHandler {

	/**
     * 
     */
	private HashMap<String, IFile> fileMap = new HashMap<String, IFile>();

	private ProjectBuilder builder;

	/**
	 * The constructor.
	 */
	public SwitchActionHandler() {

	}

	/**
	 * the command has been executed, so extract extract the needed information
	 * from the application context.
	 */
	public Object execute(ExecutionEvent event) throws ExecutionException {
		switchToTemplateOrJavaFile();
		return null;
	}

	/**
     * 
     */
	private void switchToTemplateOrJavaFile() {

		IWorkbenchPage activePage = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage();
		IEditorPart editor = activePage.getActiveEditor();
		if (editor != null) {
			IEditorInput input = editor.getEditorInput();
			if (input != null) {
				IFile currentFile = (input instanceof IFileEditorInput) ? ((IFileEditorInput) input)
						.getFile()
						: null;
				switchToTemplateOrJavaFile(currentFile);
			}
		}
	}

	/**
	 * @param contextMenuResource
	 */
	private void switchToTemplateOrJavaFile(IResource contextMenuResource) {

		IFile foundFile = null;

		if (contextMenuResource instanceof IFile) {

			String searchFilename = contextMenuResource.getName();
			IJavaProject project = JavaCore.create(contextMenuResource
					.getProject());
			builder = new ProjectBuilder(project);
			if (searchFilename.endsWith(".java")) {
				searchFilename = searchFilename.replace(".java", ".tml");
			} else if (searchFilename.endsWith(".tml")) {
				searchFilename = searchFilename.replace(".tml", ".java");
			}
			foundFile = findFile(contextMenuResource, searchFilename, project);

			if (foundFile != null && foundFile.exists()) {
				synchronizeFile(foundFile, false);
				addToCache(searchFilename, foundFile);
				EditorUtils.openFileInEditor(foundFile);
			} else {
				System.err.println("Couldn't find file: " + searchFilename);
			}
		}
	}

	/**
	 * @param contextMenuResource
	 * @param searchFilename
	 * @param project
	 * @return
	 */
	private IFile findFile(IResource contextMenuResource,
			String searchFilename, IJavaProject project) {

		IFile foundFile = null;

		foundFile = findCachedFile(searchFilename);
		if (foundFile == null) {

			if (searchFilename.endsWith(".tml")) {
				IFolder folder = builder.getFolder(ClasspathUtils
						.getTemplateFilePath(contextMenuResource));
				foundFile = folder.getFile(searchFilename);
			} else {
				foundFile = switchToFile(searchFilename, project);
			}

		}
		return foundFile;
	}

	/**
	 * @param filename
	 * @return
	 */
	private IFile findCachedFile(String filename) {
		IFile file = fileMap.get(filename);
		if (file != null) {
			synchronizeFile(file, true);
			if (file.exists() && file.isAccessible()) {
				return file;
			} else {
				fileMap.remove(filename);
				file = null;
			}
		}
		return file;
	}

	/**
	 * @param file
	 * @param force
	 */
	private void synchronizeFile(IFile file, boolean force) {

		if (force || !file.isSynchronized(IResource.DEPTH_ONE)) {
			try {
				file.refreshLocal(IResource.DEPTH_ONE, null);
			} catch (CoreException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @param searchFilename
	 * @param project
	 * @return
	 */
	private IFile switchToFile(String searchFilename, IJavaProject project) {

		IFile foundFile = null;

		foundFile = findCachedFile(searchFilename);
		if (foundFile == null) {

			try {
				IPath projectPath = project.getCorrespondingResource()
						.getLocation();
				File searchFolder = new File(projectPath.toPortableString());
				String pathURL = findFile(searchFilename, searchFolder, project);
				Path path = new Path(pathURL);
				foundFile = ResourcesPlugin.getWorkspace().getRoot()
						.findFilesForLocation(path)[0];
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
		return foundFile;
	}

	/**
	 * @param searchFilename
	 * @param foundFile
	 */
	private void addToCache(String searchFilename, IFile foundFile) {

		if (foundFile != null) {
			synchronizeFile(foundFile, false);
			fileMap.put(searchFilename, foundFile);
		}
	}

	private String findFile(String searchFilename, File searchFolder,
			IJavaProject project) {

		String path = null;
		for (File file : searchFolder.listFiles()) {

			// ignore hidden and target directories
			if (file.isDirectory() && !file.getName().startsWith(".")
					&& !file.getName().equals("target")) {

				String aPath = findFile(searchFilename, file, project);
				if (aPath != null) {
					path = aPath;
				}
			} else if (file.getName().equals(searchFilename)) {
				if (!isDerived(file, project)) {
					return file.getPath();
				}
			}
		}
		return path;
	}

	/**
	 * @param file
	 * @param project
	 * @return
	 */
	private boolean isDerived(File file, IJavaProject project) {

		Path path = new Path(file.getAbsolutePath());
		IFile ifile = project.getProject().getFile(path);
		if (ifile != null) {
			return ifile.isDerived();
		}
		return false;
	}

}
